package cn.itcast.nio.c4;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

/**
 * @author Dee
 * @date 2023/10/23
 * <p>Description: 处理write写事件
 */
@Slf4j
public class DeeWriteServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        ssc.bind(new InetSocketAddress(8080));
        while (true) {
            selector.select();
            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
            while (iter.hasNext()) {
                SelectionKey sk = iter.next();
                iter.remove();
                if (sk.isAcceptable()) {
                    // 此处因为知道一定是 ssc的连接, 因此就不再从sk中取通道
                    SocketChannel sc = ssc.accept();
                    log.info("已连接");
                    sc.configureBlocking(false);
                    SelectionKey sck = sc.register(selector, SelectionKey.OP_READ);
                    // 测试写 三百万个长度的 字符串
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < 10000000; i++) {
                        sb.append("a");
                    }
                    ByteBuffer bf = Charset.defaultCharset().encode(sb.toString());
                    // 先写一次
                    int write = sc.write(bf);
                    System.out.println(write); // 打印写了多少的长度
                    // 防止写不完, 如果还有剩余的继续写
                    if (bf.hasRemaining()) {
                        // 未写完的会触发新的事件, 因此需要关注写事件, 同时也要拿到之前的事件
                        sck.interestOps(sck.interestOps() | SelectionKey.OP_WRITE);
//                        sk.interestOps(sk.interestOps() + SelectionKey.OP_WRITE); // 两种写法
                        // 把buffer存起来
                        sck.attach(bf);
                    }
                } else if (sk.isWritable()) {
                    // 把上次未写完的buffer拿出来
                    ByteBuffer bf = (ByteBuffer) sk.attachment();
                    SocketChannel sc = (SocketChannel) sk.channel();
                    int write = sc.write(bf);
                    System.out.println(write);
                    // 如果没有数据可写了, 则删除附件, 并且去除关注的读事件
                    if (!bf.hasRemaining()) {
                        sk.attach(null);
                        sk.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE);
                    }
                }
            }
        }
    }
}
